﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace Gomoku
{
    public class AIPlayer
    {
        public enum ThreatsAndPossibilities
        {
            SELF4, SELFO3, SELFC3, SELFO2, SELFC2, ENEMYC4, ENEMYO3, ENEMYC3, ENEMYO2, ENEMYC2
        }

        public List<Specimen> population;
        public Dictionary<ThreatsAndPossibilities, int> threatsAndPossibilities = new Dictionary<ThreatsAndPossibilities, int>();        //contains count of each threat and possibility
        public Table table;                     //kind of handler to use table functions

        public AIPlayer(Table table)
        {
            this.table = table;
            population = new List<Specimen>();
            threatsAndPossibilities.Add(ThreatsAndPossibilities.SELF4, 0);
            threatsAndPossibilities.Add(ThreatsAndPossibilities.SELFO3, 0);
            threatsAndPossibilities.Add(ThreatsAndPossibilities.SELFC3, 0);
            threatsAndPossibilities.Add(ThreatsAndPossibilities.SELFO2, 0);
            threatsAndPossibilities.Add(ThreatsAndPossibilities.SELFC2, 0);
            threatsAndPossibilities.Add(ThreatsAndPossibilities.ENEMYC4, 0);
            threatsAndPossibilities.Add(ThreatsAndPossibilities.ENEMYO3, 0);
            threatsAndPossibilities.Add(ThreatsAndPossibilities.ENEMYC3, 0);
            threatsAndPossibilities.Add(ThreatsAndPossibilities.ENEMYO2, 0);
            threatsAndPossibilities.Add(ThreatsAndPossibilities.ENEMYC2, 0);
        }

        private void generatePopulation()               //generates population at the beginning of preparing move
        {
            Random r = new Random();
            for (int i = 0; i < GlobalGameConstants.specimensAmount; i++)
            {
                int x;
                int y;
                do
                {
                    x = r.Next(table.size); //table.size - size of game field.
                    y = r.Next(table.size);
                } while (!table.isPlaceEmpty(new Vector2(x, y)));

                population.Add(new Specimen(new Vector2(x, y)));
            }
        }

        private void crossover()                    //crossover function
        {
            int populationCount = population.Count;
            for (int i = 0; i < (populationCount - 1); i += 2)
            {
                Specimen newSpec1 = new Specimen(new Vector2(population.ElementAt(i).position.X, population.ElementAt(i + 1).position.Y));      //takes x from 1st specimen and y from 2nd and puts into new one
                Specimen newSpec2 = new Specimen(new Vector2(population.ElementAt(i + 1).position.X, population.ElementAt(i).position.Y));      //takes x from 2nd specimen and y from 1st and puts into 2nd new one
                if (table.isPlaceEmpty(newSpec1.position))
                    population.Add(newSpec1);
                if (table.isPlaceEmpty(newSpec2.position))
                    population.Add(newSpec2);
            }
        }

        private void mutation()                     //mutation function
        {
            int populationCount = population.Count;
            for (int i = 0; i < populationCount; i++)
            {
                Specimen newSpec = new Specimen(new Vector2(population[i].position.Y, population[i].position.X));           //creates new specimen with x = Y and y = X (X and Y comes from existing specimen)
                if (table.isPlaceEmpty(newSpec.position))
                    population.Add(newSpec);
            }
        }

        private void fitnessFunction()          //fitness function
        {
            foreach (Specimen spec in population)
            {
                foreach (var key in threatsAndPossibilities.Keys.ToList()) //clears threatsAndPossibilities dictionary
                {
                    threatsAndPossibilities[key] = 0;
                }

                table.checkTable(spec.position, ref threatsAndPossibilities);
                spec.fitness = GlobalGameConstants.self4Ratio * threatsAndPossibilities[ThreatsAndPossibilities.SELF4] + GlobalGameConstants.selfO3Ratio * threatsAndPossibilities[ThreatsAndPossibilities.SELFO3]
                    + GlobalGameConstants.selfC3Ratio * threatsAndPossibilities[ThreatsAndPossibilities.SELFC3] + GlobalGameConstants.selfO2Ratio * threatsAndPossibilities[ThreatsAndPossibilities.SELFO2]
                    + GlobalGameConstants.selfC2Ratio * threatsAndPossibilities[ThreatsAndPossibilities.SELFC2] + GlobalGameConstants.enemyC4Ratio * threatsAndPossibilities[ThreatsAndPossibilities.ENEMYC4]
                    + GlobalGameConstants.enemyO3Ratio * threatsAndPossibilities[ThreatsAndPossibilities.ENEMYO3] + GlobalGameConstants.enemyC3Ratio * threatsAndPossibilities[ThreatsAndPossibilities.ENEMYC3]
                    + GlobalGameConstants.enemyO2Ratio * threatsAndPossibilities[ThreatsAndPossibilities.ENEMYO2] + GlobalGameConstants.enemyC2Ratio * threatsAndPossibilities[ThreatsAndPossibilities.ENEMYC2];
            }
        }

        private static int compareSpecimens(Specimen spec1, Specimen spec2)         //function for List.Sort to sort specimens right
        {
            if (spec1.fitness == 0)
            {
                if (spec2.fitness == 0)
                    return 0;
                else
                    return -1;
            }
            else
            {
                if (spec2.fitness == 0)
                    return 1;
                else
                {
                    if (spec1.fitness == spec2.fitness)
                        return 0;
                    else if (spec1.fitness < spec2.fitness)
                        return -1;
                    else
                        return 1;
                }
            }

        }

        private void selection()
        {
            population.Sort(compareSpecimens);              //sorts population Ascending by fitness result
            population.Reverse();

            bool cutTail = false;

            do                              //leaves only 100 best prepared specimens
            {
                if (population.Count == GlobalGameConstants.specimensAmount)
                    cutTail = true;
                else
                {
                    population.RemoveAt(population.Count - 1);
                }
            } while (cutTail == false);
        }

        public Vector2 generateMove() //generates AI move
        {
            generatePopulation();

            for (int i = 0; i < GlobalGameConstants.numberOfGenerations; i++)
            {
                crossover();
                mutation();
                fitnessFunction();
                selection();
            }

            return population.ElementAt(0).position;
        }

        public void clearPopulation()           //need to be called at the end of move, after all operations on AI Checker position
        {
            population.Clear();
        }
    }
}
